﻿using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NDBGen.Descriptors.Columns.Tokens;
using NDBGen.Plugins.DataAccess;

namespace NDBGen.Tests
{
    [TestClass]
    public class ColumnTests : UnitTestBase<DataAccessGeneratorPluginData>
    {
        protected override DataAccessGeneratorPluginData GetPluginData(ParsedInput parsedInput)
        {
            return parsedInput.CreateDataAccessPluginData();
        }

        public Table Table { get; set; }

        [TestInitialize]
        public void TestInit()
        {
            this.Table = new Table(String.Empty, "dbo") { Name = "test" };
        }

        [TestMethod]
        public void ToStringIsReflexive()
        {
            var column = new Column(this.Table, "id", DataType.Int).AddDescriptors(new AutoGeneratedTokenDescriptor());
            var expected = "id|int|@";
            var actual = column.ToString();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_int()
        {
            var expected = new Column(this.Table, "id", DataType.Int);
            var actual = new Column(this.Table, "id|int");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_bigint()
        {
            var expected = new Column(this.Table, "id", DataType.BigInt);
            var actual = new Column(this.Table, "id|bigint");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_tinyint()
        {
            var expected = new Column(this.Table, "id", DataType.TinyInt);
            var actual = new Column(this.Table, "id|tinyint");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_bit()
        {
            var expected = new Column(this.Table, "id", DataType.Bit);
            var actual = new Column(this.Table, "id|bit");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }
        [TestMethod]
        public void Can_Parse_DataType_uniqueidentifier()
        {
            var expected = new Column(this.Table, "id", DataType.UniqueIdentifier);
            var actual = new Column(this.Table, "id|uniqueidentifier");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_datetime()
        {
            var expected = new Column(this.Table, "id", DataType.DateTime);
            var actual = new Column(this.Table, "id|datetime");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_nvarchar_with_length()
        {
            var expected = new Column(this.Table, "id", DataType.NVarChar, 50);
            var actual = new Column(this.Table, "id|nvarchar,50");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_varchar_with_length()
        {
            var expected = new Column(this.Table, "id", DataType.VarChar, 50);
            var actual = new Column(this.Table, "id|varchar,50");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_nvarchar_with_maxlength()
        {
            var expected = new Column(this.Table, "id", DataType.NVarCharMax);
            var actual = new Column(this.Table, "id|nvarchar,max");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_DataType_varchar_with_maxlength()
        {
            var expected = new Column(this.Table, "id", DataType.VarCharMax);
            var actual = new Column(this.Table, "id|varchar,max");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_Version()
        {
            var expected = new Column(this.Table, "version", DataType.Timestamp);
            var actual = new Column(this.Table, "version|timestamp");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_Money()
        {
            var expected = new Column(this.Table, "price", DataType.Money);
            var actual = new Column(this.Table, "price|money");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_SmallMoney()
        {
            var expected = new Column(this.Table, "price", DataType.SmallMoney);
            var actual = new Column(this.Table, "price|smallmoney");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_PrimaryKey()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new PrimaryKeyTokenDescriptor());
            var actual = new Column(this.Table, "id|int|*");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(AutoGeneratedTokenDescriptor))]
        public void Can_Parse_AutoGenerated()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new AutoGeneratedTokenDescriptor());
            var actual = new Column(this.Table, "id|int|@");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(PrimaryKeyTokenDescriptor), typeof(AutoGeneratedTokenDescriptor), typeof(IdentityDeclarationTokenDescriptor))]
        public void Can_Parse_PrimaryKey_AutoGenerated()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new PrimaryKeyTokenDescriptor(), new AutoGeneratedTokenDescriptor(), new IdentityDeclarationTokenDescriptor());
            var actual = new Column(this.Table, "id|int|@*");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(NullableTokenDescriptor))]
        public void Can_Parse_Nullable()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new NullableTokenDescriptor(true));
            var actual = new Column(this.Table, "id|int|?");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(DataMemberTokenDescriptor))]
        public void Can_Parse_DataMember()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new DataMemberTokenDescriptor());
            var actual = new Column(this.Table, "id|int|~");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(SubstitutedDataTypeTokenDescriptor))]
        public void Can_Parse_SubstitutedDataType()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptor<SubstitutedDataTypeTokenDescriptor>("FooType");
            var actual = new Column(this.Table, "id|int|$FooType");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(EnumIdentifierTokenDescriptor))]
        public void Can_Parse_EnumIdentifier()
        {
            var expected = new Column(this.Table, "id", DataType.VarChar, 25).AddDescriptors(new EnumIdentifierTokenDescriptor());
            var actual = new Column(this.Table, "id|varchar,25|#");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(EnumValueTokenDescriptor))]
        public void Can_Parse_EnumValue()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new EnumValueTokenDescriptor());
            var actual = new Column(this.Table, "id|int|#");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(AutoGeneratedTokenDescriptor), typeof(PrimaryKeyTokenDescriptor), typeof(IdentityDeclarationTokenDescriptor))]
        public void Can_Parse_Identity()
        {
            var expected = new Column(this.Table, "id", DataType.Int).AddDescriptors(new AutoGeneratedTokenDescriptor(), new PrimaryKeyTokenDescriptor(), new IdentityDeclarationTokenDescriptor(0, 1));
            var actual = new Column(this.Table, "id|int|@*%0,1").Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(ForeignKeyTokenDescriptor))]
        public void Can_Parse_ForeignKey()
        {
            var expected = new Column(this.Table, "product_id", DataType.Int).AddDescriptor<ForeignKeyTokenDescriptor>("catalog.products.id");
            var actual = new Column(this.Table, "product_id|int|>catalog.products.id").Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(DefaultValueTokenDescriptor))]
        public void Can_Parse_Default_Value()
        {
            var expected = new Column(this.Table, "updated_on_utc", DataType.DateTime).AddDescriptor<DefaultValueTokenDescriptor>("(getutcdate())");
            var actual = new Column(this.Table, "updated_on_utc|datetime|=(getutcdate())").Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        [ParseTestFor(typeof(ScriptedDataMetadataDescriptor))]
        public void AutoGenerated()
        {
            var column = new Column(this.Table, "id|int").Parse();
            Assert.IsTrue(column.DescriptorList.Any(t => t.GetType() == typeof(ScriptedDataMetadataDescriptor)));
        }

        [TestMethod]
        public void EnumTokensIgnoredWhenOnInvalidDataTypes()
        {
            var expected1 = new Column(this.Table, "id", DataType.Bit);
            var expected2 = new Column(this.Table, "description", DataType.VarCharMax);
            var expected3 = new Column(this.Table, "name", DataType.NVarChar, 20);
            var expected4 = new Column(this.Table, "did_it_on", DataType.DateTime);
            var expected5 = new Column(this.Table, "price", DataType.Money);

            var actual1 = new Column(this.Table, "id|bit|#");
            var actual2 = new Column(this.Table, "description|varchar,max|#");
            var actual3 = new Column(this.Table, "name|nvarchar,20|#");
            var actual4 = new Column(this.Table, "did_it_on|datetime|#");
            var actual5 = new Column(this.Table, "price|money|#");

            actual1.Parse();
            actual2.Parse();
            actual3.Parse();
            actual4.Parse();
            actual5.Parse();

            Assert.AreEqual(expected1, actual1);
            Assert.AreEqual(expected2, actual2);
            Assert.AreEqual(expected3, actual3);
            Assert.AreEqual(expected4, actual4);
            Assert.AreEqual(expected5, actual5);
        }

        [TestMethod]
        public void Can_Parse_Default_DateTime()
        {
            var expected = new Column(this.Table, "inserted_on_utc", DataType.DateTime).AddDescriptor<DefaultValueTokenDescriptor>("getutcdate()");
            var actual = new Column(this.Table, "inserted_on_utc|datetime|=getutcdate()");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_Default_Bit()
        {
            var expected = new Column(this.Table, "is_active", DataType.Bit).AddDescriptor<DefaultValueTokenDescriptor>("0");
            var actual = new Column(this.Table, "is_active|bit|=0");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Can_Parse_Multiple_Valued_Tokens()
        {
            var expected = new Column(this.Table, "other_type_id", DataType.Int)
                .AddDescriptor<ForeignKeyTokenDescriptor>("api.other_types.id")
                .AddDescriptor<SubstitutedDataTypeTokenDescriptor>("OtherType");
            var actual = new Column(this.Table, "other_type_id|int|>api.other_types.id$OtherType");
            actual.Parse();
            Assert.AreEqual(expected, actual);
        }

        [TestMethod]
        public void Parse_and_ToString_Are_Reflexive()
        {
            String[] definitions = 
            {
                "id|int",
                "id|bigint",
                "id|tinyint",
                "id|bit",
                "id|datetime",
                "id|uniqueidentifier",
                "id|varchar,50",
                "id|nvarchar,50",
                "id|varchar,max",
                "id|nvarchar,max",
                "id|int|@",
                "id|int|*",
                "id|int|?",
                "dt|datetime|=getutcdate()",
                "b|bit|=0",
                "v|version"
            };
            foreach (var definition in definitions)
            {
                var actual = new Column(this.Table, definition).Parse().ToString();
                Assert.AreEqual<String>(definition, actual);
            }
        }

        [TestMethod]
        public void ToString_and_Parse_Are_Reflexive()
        {
            Column[] definitions = 
            {
                new Column(this.Table, "id", DataType.Int),
                new Column(this.Table, "id", DataType.BigInt)
            };
            foreach (var definition in definitions)
            {
                var actual = new Column(this.Table, definition.ToString()).Parse();
                Assert.AreEqual(definition, actual);
            }
        }
    }
}